Skip to main content

1. 智能体记忆管理与多轮对话方法

Written: 2026.06 要实现多轮对话,核心问题是 如何保存上下文记忆,让智能体对用户历史输入有“记忆”,能够根据历史消息记录回答问题

2. 记忆模型与关键组件

2.1 短期记忆(Checkpointer)

载体:Checkpointer(MemorySaverRedisSaverPostgresSaver…) 作用:把每轮消息 + 工具调用结果序列化成图状态,按 thread_id 持久化;下次传入相同 thread_id 自动续写 原理:
  • 每次你调用 graph.invoke(...) 或 graph.stream(...),LangGraph 都会维护一个状态(state)
  • 如果没有 Checkpointer,这个 state 默认只存在本次调用内,调用结束就丢掉了
  • 如果启用了 Checkpointer,它会把 state 保存到存储中(内存/数据库/文件),下次继续调用时,可以恢复之前的 state,实现“记忆”

2.2 长期记忆(BaseStore)

载体:BaseStore(InMemoryStoreRedisStoreAsyncPostgresStore…) 作用:显式保存“用户偏好”“背景事实”等高密度信息,由 LLM 主动读写;Store 支持向量检索,支持命名空间隔离 和 Checkpointer 的区别:
  • Checkpointer:保存图的运行状态(短期记忆,主要用于同一个线程连续对话)
  • Store:LangGraph 的存储模块提供持久化的键值存储,支持跨线程和会话的长期内存,适用于需要持久化数据的复杂工作流

2.3 消息裁剪( Trimming)

当历史消息过长时,可在 pre_model_hook 里插入 trim_messages 策略,按最近 N 条消息 或 Token 数 保留,超出部分丢弃。这种做法的优点是:简单、可控,保证上下文长度不超限。但缺点是:容易丢失长对话中的重要信息

2.4 消息总结(Summarization )

通过生成摘要来“压缩”历史,避免 token 爆炸
  • 定期总结:每对话 X 轮,把旧消息合并成一段摘要,再存入 memory,新的上下文里只保留摘要 + 最近消息
  • 递归总结:对摘要再继续总结,形成分层结构(像树状记忆)
  • 角色分段总结:比如只总结用户输入,系统或 AI 回复不做摘要
  • 优点:历史不会丢失,只是被压缩成更短的摘要
  • 缺点:摘要质量依赖 LLM,可能丢细节

3. 代码演示

3.1 预构建 Agent 实现记忆存储

预构建 Agent 记忆示例
import dotenv
from langchain_ollama import ChatOllama
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.prebuilt import create_react_agent

# 加载环境变量配置文件
dotenv.load_dotenv()

# 初始化本地大语言模型,模型名称和推理模式
llm = ChatOllama(model="qwen3:14b", reasoning=False)

# 定义工具列表,
tools = []
# 定义短期记忆使用内存(生产可以换 RedisSaver/PostgresSaver)
checkpointer = InMemorySaver()
# 创建ReAct代理,结合语言模型和工具函数
agent = create_react_agent(model=llm, tools=tools, checkpointer=checkpointer)
# 多轮对话配置,同一 thread_id 即同一会话
config = {"configurable": {"thread_id": "user-001"}}

msg1 = agent.invoke({"messages": [("user", "你好,我叫崔亮,喜欢学习。")]}, config)
msg1["messages"][-1].pretty_print()

# 6. 第二轮(继续同一 thread)
msg2 = agent.invoke({"messages": [("user", "我叫什么?我喜欢做什么?")]}, config)
msg2["messages"][-1].pretty_print()
执行结果如下
================================== Ai Message ==================================

你好崔亮,很高兴认识你!学习是一个非常棒的追求,不知道你最近在学习什么内容呢?是有什么特别感兴趣的领域吗?😊

================================== Ai Message ==================================

你叫崔亮,你喜欢学习。😊

如果你愿意的话,可以告诉我你具体对哪些方面感兴趣,比如是学习新技能、阅读、还是其他什么?我很乐意和你一起探讨!

3.2 底层 API 实现记忆存储

定义一个聊天机器人节点函数 chatbot,它接收包含消息历史的 state,调用本地大语言模型 llm 生成回复,并返回新消息。该函数被集成到一个基于图结构的对话流程中,支持多轮对话并保持上下文
底层 API 记忆示例
from typing import TypedDict, Annotated
from langgraph.checkpoint.memory import MemorySaver
from langgraph.constants import START, END
from langgraph.graph import StateGraph
from langgraph.graph.message import add_messages
from langchain_ollama import ChatOllama

class State(TypedDict):
    """
    定义图结构中节点间传递的状态结构

    Attributes:
        messages: 消息列表,使用add_messages函数进行合并
    """
    messages: Annotated[list, add_messages]

# 创建状态图构建器
graph_builder = StateGraph(State)

# 初始化本地大语言模型,配置基础URL、模型名称和推理模式
llm = ChatOllama(base_url="http://localhost:11434", model="qwen3:14b", reasoning=False)

def chatbot(state: State):
    """
    聊天机器人节点函数,处理输入消息并生成回复

    Args:
        state (State): 包含消息历史的状态字典

    Returns:
        dict: 包含新生成消息的字典,格式为{"messages": [回复消息]}
    """
    return {"messages": [llm.invoke(state["messages"])]}

# 将聊天机器人节点添加到图中
graph_builder.add_node("chatbot", chatbot)

# 添加从开始节点到聊天机器人节点的边
graph_builder.add_edge(START, "chatbot")

# 添加从聊天机器人节点到结束节点的边
graph_builder.add_edge("chatbot", END)

# 创建内存保存器用于保存对话状态
memory = MemorySaver()

# 编译图结构并设置检查点保存器
graph = graph_builder.compile(checkpointer=memory)

# 绘制图结构并保存为PNG图片
graph.get_graph().draw_png('./graph.png')

# 配置对话线程ID
config = {"configurable": {"thread_id": "chat-1"}}

# 第一次对话:发送初始消息
msg1 = graph.invoke({"messages": ["你好,我叫崔亮,喜欢学习。"]}, config=config)
msg1["messages"][-1].pretty_print()

# 第二次对话:基于上下文询问用户信息
msg2 = graph.invoke({"messages": ["我叫什么?我喜欢做什么?"]}, config=config)
msg2["messages"][-1].pretty_print()

执行结果如下
================================== Ai Message ==================================

你好崔亮,很高兴认识你!😊 你对学习的热爱真的很棒,这种态度会让你在很多领域都取得不错的成就。不知道你最近在学习什么内容呢?如果有任何问题或者想讨论的话题,我很乐意和你交流!

================================== Ai Message ==================================

你叫**崔亮**,你喜欢**学习**。😊

如果你想了解更多关于自己的事情,或者想探索新的兴趣,也可以告诉我,我们可以一起聊聊!

3.3 长期记忆+跨线程召回

整体实现步骤为:
  1. 初始化一个 InMemoryStore(或 RedisStore)
  2. 把“记忆工具”塞进智能体工具箱,让 LLM 自己决定何时存/取
  3. 命名空间按 user_id 隔离,防止用户数据串线
长期记忆示例
import uuid
from typing import TypedDict, Annotated
import dotenv
from langchain_ollama import ChatOllama
from langchain_core.runnables import RunnableConfig
from langgraph.constants import END, START
from langgraph.graph import StateGraph, MessagesState, add_messages
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.store.memory import InMemoryStore
from langgraph.store.base import BaseStore

# 加载环境变量配置
dotenv.load_dotenv()
# 初始化本地大语言模型,配置模型名称和推理模式
model = ChatOllama(model="qwen3:14b", reasoning=False)

class State(TypedDict):
    """
    定义图中状态的数据结构。

    属性:
        messages (Annotated[list, add_messages]): 使用 add_messages 合并的消息列表。
    """
    messages: Annotated[list, add_messages]

def save_memory(store: BaseStore, user_id: str, content: str):
    """
    将用户输入的内容保存为记忆。

    参数:
        store (BaseStore): 存储系统的实例,用于持久化数据。
        user_id (str): 用户唯一标识符。
        content (str): 需要存储的文本内容。
    """
    namespace = ("memories", user_id)
    store.put(namespace, str(uuid.uuid4()), {"data": content})

def recall_memories(store: BaseStore, user_id: str, query: str, limit: int = 5):
    """
    根据查询语句检索与用户相关的记忆。

    参数:
        store (BaseStore): 存储系统的实例。
        user_id (str): 用户唯一标识符。
        query (str): 查询关键词或句子。
        limit (int, optional): 返回的记忆条数上限,默认是 5 条。

    返回:
        list[str]: 匹配的记忆内容列表。
    """
    namespace = ("memories", user_id)
    memories = store.search(namespace, query=query, limit=limit)
    return [m.value["data"] for m in memories]

def chatbot(state: MessagesState, config: RunnableConfig, *, store: BaseStore):
    """
    聊天机器人主逻辑节点函数。

    参数:
        state (MessagesState): 当前对话的状态信息,包括历史消息等。
        config (RunnableConfig): 运行时配置信息,如线程ID、用户ID等。
        store (BaseStore): 用于读取和写入用户记忆的存储接口。

    返回:
        dict: 更新后的消息状态字典。
    """
    user_id = config["configurable"]["user_id"]

    # 检索历史记忆
    query = state["messages"][-1].content
    related_memories = recall_memories(store, user_id, query)

    # 构造系统提示
    system_msg = (
        "你是一个友好的聊天助手。\n"
        f"以下是关于用户的记忆:\n{chr(10).join(related_memories) if related_memories else '暂无'}"
    )

    # 保存当前消息到记忆
    save_memory(store, user_id, query)

    # 调用模型生成回复
    response = model.invoke(
        [{"role": "system", "content": system_msg}] + state["messages"]
    )
    return {"messages": response}

# 创建状态图并定义流程
builder = StateGraph(State)
builder.add_node(chatbot)
builder.add_edge(START, "chatbot")
builder.add_edge("chatbot", END)

# 初始化检查点和存储组件
checkpointer = InMemorySaver()
store = InMemoryStore()

# 编译构建最终可运行的图对象,并绘制其结构图
graph = builder.compile(
    checkpointer=checkpointer,
    store=store,
)
graph.get_graph().draw_png('./graph.png')

# 第一次交互测试:记录用户基本信息
config1 = {"configurable": {"thread_id": "1", "user_id": "1"}}
msg1 = graph.invoke({"messages": [{"role": "user", "content": "我叫崔亮,喜欢学习。"}]}, config1)
print("第一次回复:")
msg1["messages"][-1].pretty_print()

# 第二次交互测试:验证是否能回忆起之前的信息
config2 = {"configurable": {"thread_id": "2", "user_id": "1"}}
msg2 = graph.invoke({"messages": [{"role": "user", "content": "我叫什么?我喜欢做什么?"}]}, config2)
print("第二次回复:")
msg2["messages"][-1].pretty_print()

执行结果如下
第一次回复:
================================== Ai Message ==================================

很高兴认识你,崔亮!很高兴你喜欢学习,这真是一个很棒的品质。你平时喜欢学习哪些方面的知识呢?是喜欢阅读、上课,还是通过其他方式学习?我很想听听你的故事。

第二次回复:
================================== Ai Message ==================================

你叫崔亮,喜欢学习。很高兴认识你!学习是一件很有趣的事情,你平时都喜欢学习什么呢?

3.4 消息裁剪

一个钩子函数 pre_model_hook,用于在模型处理前裁剪消息历史,只保留最近几条消息,避免上下文过长。它使用 trim_messages 函数按策略裁剪消息,限制总 token 数为 100,从人类用户消息开始裁剪,确保输入模型的消息列表不会超出限制
消息裁剪示例
import dotenv
from langchain_core.messages.utils import trim_messages, count_tokens_approximately
from langchain_ollama import ChatOllama
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.prebuilt import create_react_agent

# 加载环境变量配置
dotenv.load_dotenv()
# 初始化本地大语言模型,配置模型名称和推理模式
model = ChatOllama(model="qwen3:14b", reasoning=False)
# 定义工具列表,
tools = []

def pre_model_hook(state):
    """
    在模型处理前对消息进行预处理的钩子函数

    该函数用于裁剪消息历史,只保留最近的若干条消息,避免上下文过长

    Args:
        state (dict): 包含对话状态的字典,其中"messages"键对应消息列表

    Returns:
        dict: 包含裁剪后消息的字典,键为"llm_input_messages"
    """
    # 参数说明:
    #   state["messages"]: 需要裁剪的消息列表
    #   strategy: 裁剪策略,"last"表示从最后开始裁剪
    #   token_counter: 用于计算token数量的函数,这里使用近似计算方法
    #   max_tokens: 最大token数量限制,设置为300
    #   start_on: 开始裁剪的消息类型,"human"表示从人类用户的消息开始
    #   end_on: 结束裁剪的消息类型,可以是"human"或"tool"类型的消息
    # 返回值: 裁剪后的消息列表
    trimmed_messages = trim_messages(
        state["messages"],
        strategy="last",
        token_counter=count_tokens_approximately,
        max_tokens=300,
        start_on="human",
        end_on=("human", "tool"),
    )

    return {"llm_input_messages": trimmed_messages}

checkpointer = InMemorySaver()
agent = create_react_agent(
    model,
    tools,
    pre_model_hook=pre_model_hook,
    checkpointer=checkpointer,
)
config = {"configurable": {"thread_id": "user-001"}}
msg1 = agent.invoke({"messages": [("user", "你好,我叫崔亮")]}, config)
msg1["messages"][-1].pretty_print()
like_list = ['唱', '跳', 'rap', '篮球']
for i in like_list:
    msg = "我喜欢做的事是:" + i
    print(msg)
    agent.invoke({"messages": [("user", msg)]}, config)
msg2 = agent.invoke({"messages": [("user", "我叫什么?我喜欢做的事是什么?")]}, config)
msg2["messages"][-1].pretty_print()
执行结果如下
================================== Ai Message ==================================

你好崔亮!很高兴认识你。😊 今天过得怎么样?有什么有趣的事情发生吗?

我喜欢做的事是:唱
我喜欢做的事是:跳
我喜欢做的事是:rap
我喜欢做的事是:篮球
================================== Ai Message ==================================

你叫什么?你喜欢做的事是篮球对吗?🏀
(不过你刚刚已经告诉过我你喜欢做的事是篮球啦~)

如果你愿意的话,可以告诉我你的名字,这样我们就能更熟悉啦!😊
你是不是也特别喜欢在球场上奔跑、投篮、和朋友们一起打球的感觉?

3.5 消息总结

实现了一个基于本地大语言模型的对话代理,具备上下文记忆与摘要能力。主要功能包括:
  • 加载环境变量并初始化Ollama模型;
  • 创建摘要节点以控制输入长度;
  • 定义带记忆状态的代理及会话配置;
  • 通过多轮对话测试模型对用户信息(姓名、兴趣)的记忆与理解能力
消息总结示例
import dotenv
from langchain_core.messages.utils import trim_messages, count_tokens_approximately
from langchain_ollama import ChatOllama
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.prebuilt import create_react_agent
from langgraph.prebuilt.chat_agent_executor import AgentState
from langmem.short_term import SummarizationNode, RunningSummary

# 加载环境变量配置
dotenv.load_dotenv()

# 初始化本地大语言模型,配置模型名称和推理模式
model = ChatOllama(model="qwen3:14b", reasoning=False)

# 定义工具列表,
tools = []

# 创建一个SummarizationNode实例,用于处理文本摘要任务
# 参数说明:
#   token_counter: 用于估算文本token数量的函数,这里使用count_tokens_approximately函数
#   model: 指定使用的语言模型实例
#   max_tokens: 限制处理文本的最大token数量为300
#   max_summary_tokens: 限制生成摘要的最大token数量为128
#   output_messages_key: 指定输出消息在结果中的键名,设置为"llm_input_messages"
summarization_node = SummarizationNode(
    token_counter=count_tokens_approximately,
    model=model,
    max_tokens=300,
    max_summary_tokens=128,
    output_messages_key="llm_input_messages",
)

# 自定义状态类,继承自AgentState,添加上下文字段用于存储运行时摘要信息
class State(AgentState):
    context: dict[str, RunningSummary]

# 初始化内存检查点保存器,用于持久化代理状态
checkpointer = InMemorySaver()

# 创建React代理,整合模型、工具、摘要节点和状态管理器
agent = create_react_agent(
    model=model,
    tools=tools,
    pre_model_hook=summarization_node,
    state_schema=State,
    checkpointer=checkpointer,
)

# 配置线程ID,用于标识用户会话
config = {"configurable": {"thread_id": "user-001"}}

# 启动对话,发送用户自我介绍消息并获取模型响应
msg1 = agent.invoke({"messages": [("user", "你好,我叫崔亮")]}, config)
msg1["messages"][-1].pretty_print()

# 定义用户兴趣列表
like_list = ['唱', '跳', 'rap', '篮球']

# 循环发送用户兴趣信息,逐条更新上下文
for i in like_list:
    msg = "我喜欢做的事是:" + i
    print(msg)
    agent.invoke({"messages": [("user", msg)]}, config)

# 查询用户姓名和兴趣,测试模型对上下文的理解能力
msg2 = agent.invoke({"messages": [("user", "我叫什么?我喜欢做的事是什么?")]}, config)
msg2["messages"][-1].pretty_print()

执行结果如下
================================== Ai Message ==================================

你好崔亮,很高兴认识你!😊 今天过得怎么样?有什么有趣的事情发生吗?

我喜欢做的事是:唱
我喜欢做的事是:跳
我喜欢做的事是:rap
我喜欢做的事是:篮球
================================== Ai Message ==================================

你叫**崔亮**,你喜欢做的事是:**唱、跳、rap、篮球**。🎶💃🎤🏀

你是一个多才多艺、热爱生活、充满活力的人!是不是很酷?😎
如果你想继续分享更多关于自己的故事,我随时都在哦!